package com.bjy.qa.agent.transport.redis;

import com.bjy.qa.agent.exception.MyException;
import com.bjy.qa.agent.tools.MethodParsing;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.sql.*;
import java.util.*;

/**
 * sql 执行器
 */
public class RedisRunner {
    private static final Logger logger = LoggerFactory.getLogger(RedisRunner.class);

    private RedisPooledDataSource dataSource; // 数据源

    /**
     * 构造函数
     * @param driverClass 数据库驱动
     * @param url 数据库连接字串
     * @param port 端口号
     * @param db 数据库
     * @param password 数据库密码
     */
    public RedisRunner(String driverClass, String url, String port, String db, String password) {
        this.dataSource = new RedisPooledDataSource(driverClass, url, port, db, password);
    }

    /**
     * 执行 sql
     * @param sql sql 语句
     * @return
     * @throws Exception
     */
    public List<Map<String, Object>> execute(String sql) throws Exception {
        Jedis jedis = null;
        try {
            jedis = this.dataSource.getJedis();
            return executeBatch(jedis, sql);
        } finally {
            close(jedis);
        }
    }

    /**
     * 执行批量 sql
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @return
     * @throws SQLException
     */
    private List executeBatch(Jedis jedis, String sql) throws SQLException {
        List result = new ArrayList();

        String[] sqlList = StringUtils.split(sql, ";");
        for (String singleSql : sqlList) {
            if (StringUtils.isNotBlank(singleSql)) {
                result.add(execute(jedis, singleSql));
            }
        }

        return result;
    }

    /**
     * 执行 sql
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @return
     * @throws SQLException
     */
    private Map<String, Object> execute(Jedis jedis, String sql) throws SQLException {
        sql = sql.trim();
        Object ret = null;

        MethodParsing methodParsing = new MethodParsing(sql);
        switch(methodParsing.getMethodName().toLowerCase()){
            case "zadd" :
                ret = zadd(jedis, sql, methodParsing.getParamList());
                break;
            case "zrange" :
                ret = zrange(jedis, sql, methodParsing.getParamList());
                break;
            case "sadd" :
                ret = sadd(jedis, sql, methodParsing.getParamList());
                break;
            case "smembers" :
                ret = smembers(jedis, sql, methodParsing.getParamList());
                break;
            case "set" :
                ret = set(jedis, sql, methodParsing.getParamList());
                break;
            case "get" :
                ret = get(jedis, sql, methodParsing.getParamList());
                break;
            case "setex" :
                ret = setex(jedis, sql, methodParsing.getParamList());
                break;
            case "hset" :
                ret = hset(jedis, sql, methodParsing.getParamList());
                break;
            case "hget" :
                ret = hget(jedis, sql, methodParsing.getParamList());
                break;
            case "hgetall" :
                ret = hgetAll(jedis, sql, methodParsing.getParamList());
                break;
            case "del" :
                ret = del(jedis, sql, methodParsing.getParamList());
                break;
            case "exists" :
                ret = exists(jedis, sql, methodParsing.getParamList());
                break;
            default :
                throw new SQLException("redis 执行错误，不支持的方法。sql: " + sql);
        }

        Map result = new HashMap();
        result.put("sql", sql);
        result.put("result", ret);

        return result;
    }

    /**
     * 执行 Redis Zadd 命令，将一个或多个成员元素加入到有序集合(sorted set)当中
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     * @throws SQLException
     */
    private Object zadd(Jedis jedis, String sql, List<String> params) {
        if (params.size() < 3 || params.size() % 2 != 1) {
            throw new MyException("redis zadd 执行错误，参数个数必须是大于等于 3 的奇数。sql: " + sql);
        }

        String key = params.get(0);

        Map<String, Double> scoreMembers = new HashMap<>();
        for (int i = 1; i < params.size(); i += 2) {
            try {
                scoreMembers.put(params.get(i + 1), Double.valueOf(params.get(i)));
            } catch (NumberFormatException e) {
                throw new MyException("redis zadd 执行错误，第 " + (i + 1) + " 个参数 必须为数字。sql: " + sql);
            }
        }

        return jedis.zadd(key, scoreMembers);
    }

    /**
     * 执行 Redis Zrange 命令，返回 Redis 有序集合(sorted set)，指定区间内的成员
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     * @throws SQLException
     */
    private Object zrange(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 3) {
            throw new MyException("redis zrange 执行错误，参数个数必须为 3。sql: " + sql);
        }

        String key = params.get(0);

        long start;
        try {
            start = Long.valueOf(params.get(1));
        } catch (NumberFormatException e) {
            throw new MyException("redis zrange 执行错误，第 2 个参数 必须为数字。sql: " + sql);
        }

        long stop;
        try {
            stop = Long.valueOf(params.get(2));
        } catch (NumberFormatException e) {
            throw new MyException("redis zrange 执行错误，第 3 个参数 必须为数字。sql: " + sql);
        }

        return jedis.zrange(key, start, stop);
    }

    /**
     * 执行 Redis Sadd 命令将一个或多个成员元素加入到集合中
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object sadd(Jedis jedis, String sql, List<String> params) {
        if (params.size() < 2) {
            throw new MyException("redis sadd 执行错误，参数个数必须是大于等于 2。sql: " + sql);
        }

        String key = params.get(0);
        String[] members = params.subList(1, params.size()).toArray(String[]::new);

        return jedis.sadd(key, members);
    }

    /**
     * 执行 Redis Smembers 命令返回集合中的所有的成员
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object smembers(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 1) {
            throw new MyException("redis smembers 执行错误，参数个数必须为 1。sql: " + sql);
        }

        String key = params.get(0);

        return jedis.smembers(key);
    }

    /**
     * 执行 Redis SET 命令，用于设置给定 key 的值
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object set(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 2) {
            throw new MyException("redis set 执行错误，参数个数必须为 2。sql: " + sql);
        }

        String key = params.get(0);
        String value = params.get(1);

        return jedis.set(key, value);
    }

    /**
     * 执行 Redis Setex 命令，用于设置指定的 key 及其过期时间
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object setex(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 3) {
            throw new MyException("redis setex 执行错误，参数个数必须为 3。sql: " + sql);
        }

        String key = params.get(0);
        String value = params.get(2);

        try {
            long seconds = Long.valueOf(params.get(1));

            return jedis.setex(key, seconds, value);
        } catch (NumberFormatException e) {
            throw new MyException("redis setex 执行错误，第 2 个参数 必须为数字。sql: " + sql);
        }
    }

    /**
     * 执行 Redis Hset 命令用于为哈希表中的字段赋值
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     * @throws SQLException
     */
    private Object hset(Jedis jedis, String sql, List<String> params) {
        if (params.size() < 3 || params.size() % 2 != 1) {
            throw new MyException("redis hset 执行错误，参数个数必须是大于等于 3 的奇数。sql: " + sql);
        }

        String key = params.get(0);

        Map<String,String> hash = new HashMap<>();
        for (int i = 1; i < params.size(); i += 2) {
            hash.put(params.get(i), params.get(i + 1));
        }

        return jedis.hset(key, hash);
    }

    /**
     * 执行 Redis Hget 命令用于返回哈希表中指定字段的值
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object hget(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 2) {
            throw new MyException("redis hget 执行错误，参数个数必须为 2。sql: " + sql);
        }

        String key = params.get(0);
        String field = params.get(1);

        return jedis.hget(key, field);
    }

    /**
     * 执行 Redis Hgetall 命令用于返回哈希表中，所有的字段和值
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object hgetAll(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 1) {
            throw new MyException("redis hgetAll 执行错误，参数个数必须为 1。sql: " + sql);
        }

        String key = params.get(0);

        return jedis.hgetAll(key);
    }

    /**
     * 执行 Redis DEL 命令用于删除已存在的键
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object del(Jedis jedis, String sql, List<String> params) {
        if (params.size() == 0) {
            throw new MyException("redis del 执行错误，参数个数必须大于 0。sql: " + sql);
        }

        return jedis.del(params.toArray(String[]::new));
    }

    /**
     * 执行 Redis EXISTS 命令用于检查给定 key 是否存在
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object exists(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 1) {
            throw new MyException("redis exists 执行错误，参数个数必须为 1。sql: " + sql);
        }

        String key = params.get(0);

        return jedis.exists(key);
    }

    /**
     * 执行 Redis Get 命令，用于获取指定 key 的值
     * @param jedis 数据库连接
     * @param sql sql 语句
     * @param params 参数列表
     * @return
     */
    private Object get(Jedis jedis, String sql, List<String> params) {
        if (params.size() != 1) {
            throw new MyException("redis set 执行错误，参数个数必须为 1。sql: " + sql);
        }

        String key = params.get(0);

        return jedis.get(key);
    }

    /**
     * 关闭数据库连接
     * @param jedis 数据库连接
     */
    private void close(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}
